<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Proxy实例的方法</title>
</head>
<body>
  <script>
    // get()
    /**
     * get方法用于拦截某个属性的读取操作，可以接受三个参数，依次为目标对象，属性名和Proxy实例本身
     * (严格来说，是操作行为所针对的对象)，其中最后一个参数可选。
     * get方法的用法，上文已经有了一个例子，下面是另一个拦截读取操作的例子。
    */
   var person = {
     name: '张三'
   };
   var proxy = new Proxy(person, {
     get: function(target, propKey) {
       if (propKey in target) {
         return target[propKey];
       } else {
         throw new ReferenceError('Prop name\ "" + propKey + "\" dose not exist.');
       }
     }
   });
   proxy.name; // '张三'
   proxy.age; // 抛出一个错误
   /**
    * 上面代码表示，如果访问目标对象不存在的属性，会抛出一个错误。
    * 如果没有这个拦截器函数，访问不存在的属性，只会返回undefined
    *
    * get方法可以继承
   */
  </script>
  <script>
    var proto = new Proxy({}, {
    get(target, propertyKey, receiver) {
      console.log('GET' + propertyKey);
      return target[propertyKey];
    }
  })
  var obj = Object.create(proto);
  console.log(obj.foo); // 'GET foo'
  /**
   * 上面代码中，拦截操作定义在Prototype对象上面，所以如果读取obj对象继承的属性时，拦截会生效
   * 下面的例子使用get拦截，实现数组读取负数的索引
  */
 function createArray(...elements) {
   let handler = {
     get(target, propKey, receiver) {
       let index = Number(propKey);
       if(index < 0) {
         propKey = String(target.length + index);
       }
       return Reflect.get(target, propKey, receiver);
     }
   };
   let target = [];
   target.push(...elements);
   return new Proxy(target, handler)
 }
 let arr = createArray('a', 'b', 'c');
 arr[-1]; // c
 // 上面代码中，数组的位置参数是-上面代码中，数组的位置参数是-1，就会输出倒数第一个成员。
 // 利用Proxy，可以读取属性的操作(get), 转变为某个执行函数，从而实现属性的链式操作
 var pipe = function (value) {
   var funcStack = [];
   var oproxy = new Proxy({}, {
     get: function (pipeObject, fnName) {
      if (fnName === 'get') {
        return funcStack.reduce(function(val, fn) {
          return fn(val);
        })
      }
      funcStack.push(window[fnName]);
      return oproxy
     }
   });
   return oproxy;
 }
 var double = n => n * 2;
 var pow = n => n * n;
 var reverseInt = n => n.toString().split('').reverse().join('') | 0;
 pipe(3).double.pow.reverseInt.get; // 63
 // 上面代码设置了Proxy之后，达到了将函数名链式使用的效果。
 // 下面是一个get方法的第三个参数的例子，它总是指向原始的读操作所在的那个对象，一般情况下就是Proxy实例。
 var proxy = new Proxy({}, {
   get: function (target, key, receiver) {
     return receiver
   }
 })
 proxy.getReceiver === proxy; // true
 // 上面代码中，proxy对象的getReceiver属性是由proxy对象提供的，所以receiver指向proxy对象
 var proxy = new Proxy({}, {
   get: function(target, key, receiver) {
     return receiver;
   }
 });
 var d = Object.create(proxy);
 d.a === d; // true
 /**
  * 上面代码中，d对象本身没有a属性，所以读取d.a的时候，会去d的原型proxy对象找，
  * 这时，receiver就指向d， 代表原始的读操作所在的那个对象。
  * 如果一个属性不可配置，且不可写，则Proxy不能修改该属性，否则通过Proxy对象访问该属性会报错。
 */
var target = Object.defineProperties({}, {
  foo: {
    value: 123,
    writable: false,
    configurable: false
  }
})
var handler = {
  get(target, propKey) {
    return 'abc';
  }
}
var proxy = new Proxy(target, handler);
proxy.foo; // TypeError: Invariant check failed
</script>
<script>
  // set()方法
  /**
   * set方法用来拦截某个属性的赋值操作，可接受四个参数，依次为目标对象，属性名，属性值，和Proxy实例本身，其中
   * 最后一个参数可选，
   * 假定Person对象有一个age属性，该属性应该是一个不大于200的整数，那么可以使用Proxy保证age的属性值符合要求
  */
 var validator = {
   set: function (obj, prop, value) {
     if (prop === 'age') {
       if(!Number.isInteger(value)) {
         throw new TypeError('The age is not an integer')
       }
       if (value > 200) {
         throw new RangeError('The age seems invalid')
       }
     }
     // 对于满足条件的age属性以及其他属性，直接保存
     obj[prop] = value;
     return true
   }
 }
 var person = new Proxy({}, validator);
 person.age = 100;
 person.age; // 100
 person.age = 'young'; // 报错
 person.age = 300 // 报错
 /**
  * 上面代码中，由于设置了存值函数set，任何不符合要求的age属性赋值，都会抛出一个错误，这是数据验证的一种实现方法
  * 利用set方法，还可以绑定数据，即每当对象发生变化时，会自动更新DOM。
  * 有时，我们会在对象上面设置内部属性，属性名的第一个字符使用下划线开头，表示这些属性不应该被外部使用，
  * 结合get和set方法，就可以做到防止这些内部属性被外部读写
 */
var handler = {
  get(target, key) {
    invariant(key, 'get');
    return target[key];
  },
  set(target, key, value) {
    invariant(key, 'set');
    target[key] = value;
    return true;
  }
};
function invariant(key, action) {
  if(key[0] === '_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`)
  }
}
var target = {};
var proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
</script>
</body>
</html>